package redis

import (
	"bluebell-backend/model/po"
	"errors"
	"github.com/go-redis/redis"
	"math"
	"strconv"
	"time"
)

// 推荐阅读
// 基于用户投票的相关算法：http://www.ruanyifeng.com/blog/algorithm/

// 本项目使用简化版的投票分数
// 投一票就加432分   86400/200  --> 200张赞成票可以给你的帖子续一天

/* 投票的几种情况：
   direction=1时，有两种情况：
   	1. 之前没有投过票，现在投赞成票    --> 更新分数和投票记录  差值的绝对值：1  +432
   	2. 之前投反对票，现在改投赞成票    --> 更新分数和投票记录  差值的绝对值：2  +432*2
   direction=0时，有两种情况：
   	1. 之前投过反对票，现在要取消投票  --> 更新分数和投票记录  差值的绝对值：1  +432
	2. 之前投过赞成票，现在要取消投票  --> 更新分数和投票记录  差值的绝对值：1  -432
   direction=-1时，有两种情况：
   	1. 之前没有投过票，现在投反对票    --> 更新分数和投票记录  差值的绝对值：1  -432
   	2. 之前投赞成票，现在改投反对票    --> 更新分数和投票记录  差值的绝对值：2  -432*2

   投票的限制：
   每个贴子自发表之日起一个星期之内允许用户投票，超过一个星期就不允许再投票了。
   	1. 到期之后将redis中保存的赞成票数及反对票数存储到mysql表中
   	2. 到期之后删除那个 KeyPostVotedZSetPF
*/

const (
	oneWeekInSeconds = 7 * 24 * 3600
	scorePerVote     = 432 // 每一票值多少分
)

var (
	ErrVoteTimeExpire = errors.New("投票时间已过")
	ErrVoteRepeated   = errors.New("不允许重复投票")
)

func InsertPost(post *po.Post) error {
	pipeline := client.TxPipeline()
	// 帖子时间
	pipeline.ZAdd(GetRedisKey(KeyPostTimeZSet), redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: post.PId,
	})
	// 帖子分数
	pipeline.ZAdd(GetRedisKey(KeyPostScoreZSet), redis.Z{
		Score:  float64(time.Now().Unix()),
		Member: post.PId,
	})

	// 更新：把帖子id加到社区的set
	cKey := GetRedisKey(KeyCategorySetPF + strconv.Itoa(int(post.Cid)))
	pipeline.SAdd(cKey, post.PId)
	_, err := pipeline.Exec()
	return err
}

func VoteForPost(userID, pId string, value float64) error {
	// 1. 判断投票限制
	// 去redis取帖子发布时间
	postTime := client.ZScore(GetRedisKey(KeyPostTimeZSet), pId).Val()
	if float64(time.Now().Unix())-postTime > oneWeekInSeconds {
		return ErrVoteTimeExpire
	}

	// 2. 更新贴子的分数
	// 先查当前用户给当前帖子的投票记录
	ov := client.ZScore(GetRedisKey(KeyPostVotedZSetPF+pId), userID).Val()

	// 如果当前的ov等于value 说明重复投票
	if ov == value {
		return ErrVoteRepeated
	}
	// op控制正负号
	var op float64
	if value > ov {
		// 说明是+分
		op = 1
	} else {
		op = -1
	}
	diff := math.Abs(value - ov)
	pipeline := client.TxPipeline()
	pipeline.ZIncrBy(GetRedisKey(KeyPostScoreZSet), op*diff*scorePerVote, pId)

	// 3. 记录用户为该贴子投票的数据
	if value == 0 {
		// 取消投票，移除
		pipeline.ZRem(GetRedisKey(KeyPostVotedZSetPF+pId), userID)
	} else {
		pipeline.ZAdd(GetRedisKey(KeyPostVotedZSetPF+pId), redis.Z{
			Score:  value, // 赞成票还是反对票
			Member: userID,
		})
	}
	_, err := pipeline.Exec()
	return err

}
